﻿open System
open System.Collections.Generic
open System.Linq
open System.Net
open System.Text
open System.Text.RegularExpressions
open System.IO
open System.Xml.Linq

[<STAThreadAttribute>]
[<EntryPoint>]
let main argv = 
    if argv.Count() = 0 then
        Console.WriteLine("")
        Console.WriteLine("Settings file path missing")
        Console.WriteLine("")
        Console.WriteLine(@"example LinkGrabber.exe ""c:\users\yourusername\desktop\settings.txt""")
        Console.WriteLine("")
        Console.WriteLine("")
        Console.WriteLine("Settings example")
        Console.WriteLine("")
        Console.WriteLine(@"UserName=youruser
PassWord=yourpassword
TargetURI=http://forum.soundarea.org/index.php?/topic/849083-07-09-2012-0day-mp3-releases/
GenresToExlude=black metal;death metal
AllowedHosts=ul.to;rapidshare.com;www.rapidshare.com
IncludeVariusArtist=false
IncludeRips=false")
        Environment.Exit(0)

    let settingsFilePath = "settings.txt"

    if not(File.Exists(settingsFilePath)) then
        Console.WriteLine("")
        Console.WriteLine("settings.txt not found")
        Environment.Exit(0)

    let settings = Settings.GetSettings settingsFilePath
    //let settings = Settings.GetSettings @"C:\users\paolo\desktop\settings.txt"

    let userName = Settings.GetValue settings Settings.UserName
    let passWord = Settings.GetValue settings Settings.PassWord
    //let targetURI = new Uri(Settings.GetValue settings Settings.TargetURI)
    let targetURI = new Uri(argv.First())
    let genresToExlude = (Settings.GetValue settings Settings.GenresToExlude).Split(';')
    let allowedHosts = (Settings.GetValue settings Settings.AllowedHosts).Split(';')
    let includeVariusArtist = Convert.ToBoolean(Settings.GetValue settings Settings.IncludeVariusArtist)
    let includeRips = Convert.ToBoolean(Settings.GetValue settings Settings.IncludeRips)

    //httprequest settings
    let userAgent = "Opera/9.80 (Windows NT 6.1; WOW64; U; it) Presto/2.10.289 Version/12.00"
    let accept = "text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/webp, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1"
    let acceptLang = "it-IT,it;q=0.9,en;q=0.8"
    let acceptEnc = DecompressionMethods.Deflate ||| DecompressionMethods.GZip;
    let domain = "forum.soundarea.org"
    let loginURI = new Uri("http://forum.soundarea.org/index.php?app=core&module=global&section=login")
    let processLoginURI = new Uri("http://forum.soundarea.org/index.php?app=core&module=global&section=login&do=process")

    let parseAttr str =
        (System.Xml.Linq.XDocument.Parse(str).Root.Attributes()
            |> Seq.filter( fun a -> a.Name.ToString().ToLower() = "value")).First().Value

    let parseGenre (html:string) =
        let xdoc = System.Xml.Linq.XDocument.Parse("<root>" + html + "</root>").Root.Descendants()
        let genre = (xdoc |> Seq.filter( fun d -> d.Name.ToString().ToLower() = "span")).First().Value
        let linksList = (xdoc |> Seq.filter( fun d -> d.Name.ToString().ToLower() = "pre")).First().Value
        (genre, linksList)

    let filterLink (link:string) : string =
        let fileName = Path.GetFileName(link)
        let hostFilter =
            if (allowedHosts |> Seq.filter(fun host ->Regex.Match(link, String.Format("http[s]?://{0}", host), RegexOptions.IgnoreCase).Success)).Count() > 0 || allowedHosts.Count() = 0 then
                String.Empty
            else
                "HOST NOT ALLOWED"
        let variusArtistFilter =
            if includeVariusArtist || not(Regex.Match(link, "/(va|Various_Artists)[_]?-[_]?", RegexOptions.IgnoreCase).Success) then
                String.Empty
            else
                "VARIOUS ARTISTS MATCHED"
        let ripsFilter =
            if includeRips  || not(Regex.Match(link, "(([0-9]{2}(-|_)[0-9]{2}(-|_)((SAT|CABLE|LINE|DVBC|DVBS|FM|SBD)(-|_))?)|((SAT|CABLE|LINE|DVBC|DVBS|FM|SBD)(-|_)))[0-9]{2,4}", RegexOptions.IgnoreCase).Success) then
                String.Empty
            else
                "RIPS MATCHED"
        let error = hostFilter + " " + variusArtistFilter + " " + ripsFilter + " "
        let name = Path.GetFileNameWithoutExtension(link) 
        if error.Trim() = String.Empty then
            name + " \t\t " + link + "\r\n\r\n"
        else
            name + "\r\n " + error + "\r\n\r\n"

    let getLoginStuff =
        let httpWReq = WebRequest.Create(loginURI) :?> HttpWebRequest
        httpWReq.ServicePoint.Expect100Continue  <- false
        httpWReq.CookieContainer <- new CookieContainer()
        httpWReq.UserAgent <- userAgent
        httpWReq.Accept <- accept
        httpWReq.AutomaticDecompression <- acceptEnc
        httpWReq.Headers.Add("Accept-Language", acceptLang)
        use response = httpWReq.GetResponse() :?> HttpWebResponse
        //response.Cookies.Cast<Cookie>() |> Seq.iter (fun f -> printfn "%s%s" f.Name f.Value)
        Console.WriteLine(response.StatusDescription)
        use dataStream = response.GetResponseStream()
        use reader = new StreamReader(dataStream)
        let responseFromServer = reader.ReadToEnd()
        let rx = Regex.Match(responseFromServer, "<input[\s]*type=('|\")hidden('|\")[\s]*name=('|\")auth_key('|\")[^/>]+/>", RegexOptions.IgnoreCase)
        let auth_key =
            (rx.Captures.Cast<Match>() |> Seq.map ( fun f -> parseAttr(f.Value))).First()
        (response.Cookies, auth_key)

    let cookies = fst(getLoginStuff)
    let auth_key = snd(getLoginStuff)
    //cookies.Cast<Cookie>() |> Seq.iter  (fun f -> printfn "%s: %s %s" f.Name f.Value f.Domain)

    let loginCookies =
        let httpWReq = WebRequest.Create(processLoginURI) :?> HttpWebRequest
        let encoding = new ASCIIEncoding()
        let postData = String.Format("auth_key={0}&ips_username={1}&ips_password={2}&rememberMe=1", auth_key, userName, passWord)
        let data = encoding.GetBytes(postData)
        httpWReq.ServicePoint.Expect100Continue  <- false
        httpWReq.Method <- "POST"
        httpWReq.Referer <- "http://forum.soundarea.org/index.php?app=core&module=global&section=login"
        httpWReq.UserAgent <- userAgent
        httpWReq.Accept <- accept
        httpWReq.Headers.Add("Accept-Language", acceptLang)
        httpWReq.AutomaticDecompression <- acceptEnc
        httpWReq.KeepAlive <- true
        httpWReq.ContentType <- "application/x-www-form-urlencoded";
        httpWReq.ContentLength <- Convert.ToInt64(data.Length)
        httpWReq.CookieContainer <- new CookieContainer()
        cookies.Cast<Cookie>() |> Seq.iter  (fun f -> printfn "%s: %s %s" f.Name f.Value f.Domain)
        httpWReq.CookieContainer.Add(cookies)
        use newStream = httpWReq.GetRequestStream()
        newStream.Write(data,0,data.Length)
        newStream.Close()
        use response = httpWReq.GetResponse() :?> HttpWebResponse
        Console.WriteLine(response.StatusDescription)
        use dataStream = response.GetResponseStream()
        use reader = new StreamReader(dataStream)
        let responseFromServer = reader.ReadToEnd()
        response.Cookies

    let parsePage (uri:Uri) =
        let httpWReq = WebRequest.Create(uri) :?> HttpWebRequest
        httpWReq.ServicePoint.Expect100Continue  <- false
        httpWReq.CookieContainer <- new CookieContainer()
        httpWReq.CookieContainer.Add(loginCookies)
        httpWReq.UserAgent <- userAgent
        httpWReq.Accept <- accept
        httpWReq.AutomaticDecompression <- acceptEnc
        httpWReq.Headers.Add("Accept-Language", acceptLang)
        use response =
            try
                httpWReq.GetResponse() :?> HttpWebResponse
            with
                :? System.Net.WebException ->
                    Console.WriteLine ("Error during the request, maybe username or password are incorrect or the link is wrong.")
                    Environment.Exit(0)
                    null
        //response.Cookies.Cast<Cookie>() |> Seq.iter (fun f -> printfn "%s%s" f.Name f.Value)
        Console.WriteLine(response.StatusDescription)
        use dataStream = response.GetResponseStream()
        use reader = new StreamReader(dataStream)
        let responseFromServer = reader.ReadToEnd()
        let rx = Regex.Matches(responseFromServer, "<span[\s]*[^/]+/span>[\s|.]*<br[\s]*/>[\s|.]*<pre[\s]*class=('|\")prettyprint('|\")[\s]*>[^<]+</pre>", RegexOptions.IgnoreCase)
        seq {
            for t in rx.Cast<Match>() do
            yield parseGenre(t.Value)
        }


    let linksText =
        let genresFiltered =
            parsePage targetURI
                |> Seq.sortBy fst
                |> Seq.groupBy fst
                |> Seq.map (fun (key, group) -> key, String.Join("\n", Seq.map snd group))
                |> Seq.filter(fun t -> not(Array.Exists(genresToExlude, fun g -> g = fst(t).ToLower())))
        String.Join("\n", Seq.map snd genresFiltered)

    let linksList = 
        linksText.Split('\n')
            |> Seq.filter( fun l -> l.Trim() <> String.Empty )
            |> Seq.map( fun l -> filterLink l )
            |> Seq.sortBy( fun l -> l)

    let finalString =
        let s = String.Join("\r\n", linksList)
        if s = null then
            String.Empty
        else
            s

    if finalString.Length > 0 then
        System.Windows.Forms.Clipboard.SetText(String.Join("\r\n", linksList))
        Console.WriteLine("The Links are in your clipboard.")
    else
        Console.WriteLine("There are no link to parse. Maybe the link is incorrect or your settings don't match any link.")
    Console.ReadLine() |> ignore
    0